package project.newCode.surfaces;

import java.awt.Color;

import project.hulpPakket.Color3f;
import project.hulpPakket.Matrix4f;
import project.hulpPakket.Point3f;
import project.hulpPakket.TexCoord2f;
import project.hulpPakket.Vector3f;
import project.newCode.Hitrecord;
import project.newCode.Ray;
import project.newCode.materials.Material;

public class Sphere extends Surface{

	float radius;
	Point3f center;
	Matrix4f inverse;
	Matrix4f transformation;


	public Sphere(float nradius, Point3f ncenter, Material m)
	{
		super(m);
		radius = nradius;
		center = ncenter;
		inverse = new Matrix4f();
		inverse.setIdentity();
	}

	@Override
	public boolean hit(Ray ray, double t0, double t1, Hitrecord rec) {
		Ray r = Matrix4f.transformRay(this.inverse, ray);
		Point3f e = r.getOrigin();
		Vector3f d = r.getDirection();
		double A = Vector3f.Inp(d, d);
		double B = 2*Vector3f.Inp(d, Point3f.Min(e, center));
		double C =  (Vector3f.Inp(Point3f.Min(e, center),Point3f.Min(e, center))-Math.pow(getRadius(),2));
		double Discriminant = Math.pow(B, 2)-4*A*C;
		if (Discriminant <0) return false;
		if (Discriminant >0) {
			Vector3f eminc = Point3f.Min(e, center);
			double t2 = (-Vector3f.Inp(d, eminc)+Math.sqrt(Math.pow(Vector3f.Inp(d, eminc),2)-Vector3f.Inp(d, d)*(Vector3f.Inp(eminc, eminc)-Math.pow(this.getRadius(), 2))));
			double t3 = (-Vector3f.Inp(d, eminc)-Math.sqrt(Math.pow(Vector3f.Inp(d, eminc),2)-Vector3f.Inp(d, d)*(Vector3f.Inp(eminc, eminc)-Math.pow(this.getRadius(), 2))));
			t2 = t2/Vector3f.Inp(d, d);
			t3 = t3/Vector3f.Inp(d, d);
			double t4 = Math.min(t2, t3);
			double t5 = Math.max(t2, t3);
			if(t4 > t0 && t4 < t1) 
			{ 
				rec.t = (float) t4; 
			}
			else if(t5 > t0 && t5 < t1) 
			{ 
				rec.t = (float) t5; 
			}
			else 
			{ 
				return false;
			}
			rec.intersection = Point3f.Plus(e,d.multiply(rec.t));
			rec.normal = Point3f.Min(rec.intersection, center).divide(this.getRadius());
			float phi = (float) Math.acos(rec.normal.z-0.00001*Math.signum(rec.normal.z));
			float theta = (float) (Math.acos((rec.normal.x-0.00001*Math.signum(rec.normal.x))/Math.sin(phi))/(2*Math.PI));
			if(rec.normal.y>0) {
				theta=1-theta;
			}
			rec.tex = new TexCoord2f(theta,(float) (phi/Math.PI));
			this.setMaterialProperties(rec);
			rec.normal = new Vector3f(Matrix4f.prod3p(inverse.transpose(), new Point3f(rec.normal))).normalise();
			rec.intersection = Point3f.Plus(ray.getOrigin(),ray.getDirection().multiply((rec.t)));
			return true;
		}
		return false;

	}
	@Override
	// NOTE: the inverse transformation is saved, to be applied to the the ray, so 
	public Sphere Transform(Matrix4f t, Matrix4f i) {
		Sphere s = new Sphere(this.radius,this.center,this.getMaterial());
		s.setInverse(i);
		s.setTransformation(t);
		return s;
	}

	@Override
	public Box boundingbox() {
		Point3f [] points = new Point3f[8];
		for(int i=0;i<8;i++) {
			points[i] = new Point3f((float) (this.center.x+Math.pow(-1, i%2)*getRadius()), (float) (this.center.y+Math.pow(-1,Math.floor(i/2)%2)*getRadius()),(float) (this.center.z+Math.pow(-1, Math.floor(i/4)%2)*getRadius()));
			points[i] = Matrix4f.prod3p(getTransformation(), points[i]);
		}
		Point3f max = points[0].clone();
		Point3f min = points[0].clone();
		for(int j=0; j<8; j++) {
			if(points[j].x > max.x) max.x=points[j].x;
			if(points[j].y > max.y) max.y=points[j].y;
			if(points[j].z > max.z) max.z=points[j].z;
			if(points[j].x < min.x) min.x=points[j].x;
			if(points[j].y < min.y) min.y=points[j].y;
			if(points[j].z < min.z) min.z=points[j].z;
		}
		return new Box(min,max);
	}

	public Point3f getCenter() {
		return center;
	}

	public void setCenter(Point3f center) {
		this.center = center;
	}

	public float getRadius() {
		return radius;
	}

	public void setRadius(float radius) {
		this.radius = radius;
	}

	@Override
	public Surface surfaceClone() {
		return new Sphere(this.radius,this.center.clone(),this.getMaterial());
	}

	public Matrix4f getInverse() {
		return inverse;
	}

	public void setInverse(Matrix4f inverse) {
		this.inverse = inverse;
	}

	@Override
	public Matrix4f getTransformation() {
		return transformation;
	}

	public void setTransformation(Matrix4f transformation) {
		this.transformation = transformation;
	}

}
